package com.soonoh.simplecube;

import java.nio.FloatBuffer;
import java.util.Stack;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLU;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;

import com.example.android.apis.graphics.spritetext.Projector;
import com.soonoh.simplecube.cube.ArcBall;
import com.soonoh.simplecube.cube.BigCube;
import com.soonoh.simplecube.cube.Cube;
import com.soonoh.simplecube.cube.CubeRotation;
import com.soonoh.simplecube.cube.Matrix3f;
import com.soonoh.simplecube.cube.Matrix4f;
import com.soonoh.simplecube.cube.Point2f;
import com.soonoh.simplecube.cube.Quat4f;
import com.soonoh.simplecube.cube.Vector3f;

public class CubeView extends GLTutorialBase {
    
    int CUBE_WIDTH = 2;
    
    float[] boxVertices = new float[] {
            //front
            -1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            //left
            -1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, 1.0f,
            -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            //back
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            //right
            1.0f, -1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            //top
            -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, 1.0f, 1.0f,
            //bottom
            -1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, 1.0f
    };

    FloatBuffer boxBuffer;

    float normals[] = {
            0.0f, 0.0f, 1.0f,
            -1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, -1.0f,
            1.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f,
            0.0f, -1.0f, 0.0f,
    };


    float texCoords[] = new float[] {
            // FRONT
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
            // LEFT
            1.0f, 0.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            0.0f, 1.0f,
            // BACK
            1.0f, 0.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            0.0f, 1.0f,
            // RIGHT
            1.0f, 0.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            0.0f, 1.0f,
            // TOP
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
            // BOTTOM
            1.0f, 0.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            0.0f, 1.0f
    };

    FloatBuffer texBuff;
    Bitmap[] cubeFaceTextures = new Bitmap[Cube.CUBE_FACE_SIZE];
    
    Matrix4f Transform = new Matrix4f();
    Matrix3f LastRot = new Matrix3f();
    Matrix3f ThisRot = new Matrix3f();
    Point2f mousePt = new Point2f();
    Point2f oldMousePt = new Point2f();
    boolean isClicked = false;
    boolean isDragging = false;
    ArcBall arcBall = new ArcBall();
    private BigCube bigCube = new BigCube();

    /* Holds X, Y, Z axis rotation ratio used for cube rotation. */
    float[] rotateAxes = { 0.0f, 0.0f, 0.0f };
    float rquad = 0.0f; // Angle For The Quad
    float rotate_speed = 1.8f;
    boolean g_isRotated = false;
    CubeRotation currentRotation;

    // for selection

    /*
     * -1 : not selected  each cube has 6 face names, the selected cube names.
     */
    // selected cube face.
    int g_selected_cube_face = 0;
    boolean g_cube_selected = false;
    int[][] g_arrowOperations = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };
    int[] selectedCubeLocation = new int[3];
    
    boolean isTouchDown = false;
    boolean[] isVisibleFaces = {true, true, true, true, true, true};
    float[][] pickableFaces = null;
    int pickFace = 0;
    
    
    long time = 0L;
    int rotCounter = 0;
    
    Stack<CubeRotation> rotationStack = new Stack<CubeRotation>();

    Projector projector = new Projector();

	private boolean started = false;

	private boolean hasNotice = false;

    public CubeView(Context c, AttributeSet attr) {
        super(c, attr, 20);
        boxBuffer = makeFloatBuffer(boxVertices);
        rotate_speed = 90.0f / fps*5;
        texBuff = makeFloatBuffer(texCoords);
        int[] resourceIds = {
                R.drawable.cube_front, R.drawable.cube_left, R.drawable.cube_back,
                R.drawable.cube_right, R.drawable.cube_top, R.drawable.cube_bottom
        };
        for (int i = 0 ; i < cubeFaceTextures.length; ++i) {
            cubeFaceTextures[i] = BitmapFactory.decodeResource(c.getResources(), resourceIds[i]);
        }
        setFocusable(true);
    }
    
    protected void init(GL10 gl) {
        
        gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glEnable(GL10.GL_CULL_FACE);
        gl.glDepthFunc(GL10.GL_LEQUAL);
        gl.glClearDepthf(1.0f);
        gl.glShadeModel(GL10.GL_SMOOTH);

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

        gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        for (int i = 0; i < cubeFaceTextures.length; ++i) {
            loadTexture(gl, cubeFaceTextures[i]);
        }
        gl.glClearColor(0.0f, 0.5f, 0.0f, 0.0f);
        int scissorMarginX = width/8;
        int scissorMarginY = height/8;
//        gl.glEnable(GL10.GL_SCISSOR_TEST);
        gl.glScissor(scissorMarginX, scissorMarginY, width-scissorMarginX*2, height-scissorMarginY*2);
        
        requestFocus();
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);

        mousePt.x = event.getX();
        mousePt.y = event.getY();
        int action = event.getAction();
        switch (action) {
        case MotionEvent.ACTION_CANCEL:
            isClicked = false;
            break;
        case MotionEvent.ACTION_DOWN:
            isClicked = false;
            g_cube_selected=false;
            oldMousePt.x = event.getX();
            oldMousePt.y = event.getY();
            isTouchDown = true;
            setPickFace();
            break;
        case MotionEvent.ACTION_MOVE:
        	if (pickFace >= 0) {
                if (g_cube_selected) {
                    // note that y coordinate is reversed by contrast with x coordinate.
                    Vector3f motionVector = new Vector3f( mousePt.x - oldMousePt.x, oldMousePt.y - mousePt.y, 0.0f );
                    boolean isMotion = doMotionEffect(motionVector);
                    g_cube_selected = false;
                    isTouchDown = false;
                }
        	} else {
        		isClicked = true;
        	}
            break;
        case MotionEvent.ACTION_UP:
            isClicked = false;
            break;
        }
        return true;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
    	alertMessage( "down keyCode: " + keyCode + ", event code:" + event.getKeyCode());
    	switch (keyCode) {
    	case KeyEvent.KEYCODE_S:
    		startGame(20, null);
    		break;
    	default:
    		return super.onKeyDown(keyCode, event);
    	}
    	return true;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        arcBall.setBounds(w, h);
        projector.setCurrentView(0, 0, w, h);
        Log.d("Cube", "onSizeChange w="+w+",h="+h);
    }
    
    public void shuffle(int times) {
      CubeRotation.initRandom();
      for (int i = 0; i < times; ++i) {
          rotationStack.push(CubeRotation.random());
      }
    }

    public void startGame(int i, String message) {
    	if (message != null ) {
    		alertMessage(message);
    	}
    	shuffle(i);
    	started = true;
    }

    protected void drawFrame(GL10 gl) {
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        GLU.gluLookAt(gl, 0, 0, 26, 0, 0, 0, 0, 1, 0);

        boolean isDoPicking = isTouchDown && pickFace >= 0;
        if (isDoPicking) g_cube_selected = false;
        if (! isDoPicking && !g_cube_selected) {
        if (!isDragging) // Not Dragging
        {
            if (isClicked) // First Click
            {
                isDragging = true; // Prepare For Dragging
                ThisRot.copyTo(LastRot);
                arcBall.click(mousePt); // Update Start Vector And Prepare For Dragging
            }
        } else {
            if (isClicked) // Still Clicked, So Still Dragging
            {
                Quat4f ThisQuat = new Quat4f();

                arcBall.drag(mousePt, ThisQuat); // Update End Vector And Get Rotation As Quaternion
                ArcBall.Matrix3fSetRotationFromQuat4f(ThisRot, ThisQuat); // Convert Quaternion Into Matrix3fT
                ArcBall.Matrix3fMulMatrix3f(ThisRot, LastRot); // Accumulate Last Rotation Into This One
                ArcBall.Matrix4fSetRotationFromMatrix3f(Transform, ThisRot); // Set Our Final Transform's Rotation From This One

            } else {
                // No Longer Dragging
                isDragging = false;
            }
        }
        }

        if (currentRotation != null && g_isRotated && rquad == 0.0f) {
            if (currentRotation.IsRotateBasedOnEyePoint()) {
                //TODO: implement later.
//                convertedAxis(currentRotateOperation);
            }
            Log.d("Cube", "currentRotationOperation:" + currentRotation);
            rotateAxes[0] = rotateAxes[1] = rotateAxes[2] = 0.0f;
            rotateAxes[currentRotation.getAxis()] = currentRotation.getDirection();
            Log.d("Cube", "rotateAxes: " + rotateAxes[0] + ", " + rotateAxes[1] + ", " + rotateAxes[2]);
        }

        gl.glPushMatrix();

        gl.glMultMatrixf(Transform.m, 0); //Apply Dynamic Transform

        int[] indices = { 0, 0, 0 };
        if (pickableFaces == null || isDragging) {
        	calculateVisibleFaces(gl);
        }

        for (indices[0] = 0; indices[0] < BigCube.SIZE; ++indices[0]) {
            for (indices[1] = 0; indices[1] < BigCube.SIZE; ++indices[1]) {
                for (indices[2] = 0; indices[2] < BigCube.SIZE; ++indices[2]) {
                    int tx, ty, tz;
                    tx = indices[0] * CUBE_WIDTH - BigCube.BIG_CUBE_LAST_INDEX;
                    ty = indices[1] * CUBE_WIDTH - BigCube.BIG_CUBE_LAST_INDEX;
                    tz = indices[2] * CUBE_WIDTH - BigCube.BIG_CUBE_LAST_INDEX;
                    gl.glPushMatrix();
                    if (currentRotation != null && g_isRotated) {
                        if (indices[currentRotation.getAxis()] == currentRotation.getColumn()) {
                            gl.glRotatef(rquad, rotateAxes[0], rotateAxes[1], rotateAxes[2]);
                        }
                    }
                    gl.glTranslatef(tx, ty, tz);

                    // don't draw inner small cube.
                    Cube cube = bigCube.getCube(indices[0], indices[1], indices[2]);
                    if (cube != null) {
                        drawOneCube(gl, isDoPicking, indices[0], indices[1], indices[2]);
                        if (isDoPicking && g_cube_selected) {
                            // note that y coordinate is reversed by contrast with x coordinate.
                            Vector3f motionVector = new Vector3f( mousePt.x - oldMousePt.x, oldMousePt.y - mousePt.y, 0.0f );
                            boolean isMotion = doMotionEffect(motionVector);
                            g_cube_selected = false;
                            isTouchDown = false;
                        }
                    }
                    gl.glPopMatrix();
                }
            }
        }
        if (isDoPicking) {
        	isTouchDown = false;
        }


        // update rotation.
        if (g_isRotated) {
            if (rquad < 90.0f) {
                rquad += rotate_speed;
            }
        }

        boolean isPerfect = false;
        if (rquad >= 90.0f) {
            /* is end of rotation? if then, finish rotation,
             * whole cube state are supposed to be changed as same state of being displayed.
             */
            if (g_isRotated) {
                bigCube.rotate(currentRotation);
                ++ rotCounter;
                
                g_isRotated = false;
                rquad = 0.0f;
                if (rotationStack.empty() && started) {
                	started = false;
                	time = SystemClock.uptimeMillis();
                }
                isPerfect = bigCube.isPerfectCube();
                if (isPerfect) {
                	time = SystemClock.uptimeMillis() - time;
                	hasNotice = true;
                }
            }
        }
        // if there are more rotations, do rotation, else reset rotation variables.
        if (! g_isRotated && !rotationStack.empty()) {
            performRotation();
        }

        gl.glPopMatrix();
        if (hasNotice) {
        	hasNotice = false;
        	Intent i = new Intent(getContext(), ResultGame.class);
        	i.putExtra("time", time);
        	i.putExtra("counter", rotCounter);
        	getContext().startActivity(i);
        }
    }

	private void performRotation() {
        rquad = 0.0f;
        g_isRotated = true;
        currentRotation = rotationStack.pop();
    }

    float[][] cubeColours = {
        { 0.0f, 1.0f, 0.0f },
        { 1.0f, 0.2f, 0.2f },
        { 0.2f, 0.2f, 1.0f },
        { 1.0f, 0.6f, 0.0f },
        { 1.0f, 1.0f, 0.0f },
        { 1.0f, 1.0f, 1.0f },
        
        { 0.0f, 0.0f, 0.0f },
        { 0.5f, 0.0f, 0.5f } };

    void calculateVisibleFaces(GL10 gl) {
    	if (pickableFaces== null ) {
    		pickableFaces = new float[Cube.CUBE_FACE_SIZE][4];
    	}
    	Matrix4f arcRotateInverted = Transform.invert();
    	int face = 0;
    	for (int i = 0, size=Cube.CUBE_FACE_SIZE*3; i < size; ++i) {
    		float[] normalVec = {normals[i], normals[++i], normals[++i], 1.0f};
    		normalVec = arcRotateInverted.multiply(normalVec, 4);
    		/* if the normal vector of the face is see far space, it is not displayed */
    		isVisibleFaces[face] = (normalVec[2] > 0);
    		if (isVisibleFaces[face]) {
    			float[] quardPoints = getProjectedPoints(gl, face, 3.0f);
    			pickableFaces[face] = quardPoints;
    		} else {
    			pickableFaces[face] = null;
    		}
    		++ face;
    	}
    }

    void setPickFace() {
    	pickFace = -1;
    	float pickX = oldMousePt.x;
    	float pickY = height - oldMousePt.y;
    	for (int i = 0; i < Cube.CUBE_FACE_SIZE; ++i) {
    		if (pickableFaces[i] != null) {
    			boolean isInner = isInnerOfQuadrangle(pickX, pickY, pickableFaces[i]);
    			if (isInner) {
    				pickFace = i;
    				break;
    			}
    		}
    	}
    	if (pickFace == -1) {
    		g_cube_selected = false;
    	}
    }

    void drawOneCube(GL10 gl, boolean isDoPicking, int x, int y, int z) {

        Cube cube = bigCube.getCube(x, y, z);
        if (cube == null) return;
        int[] currentCubeColours = cube.getColours();

        for (int i = 0; i < Cube.CUBE_FACE_SIZE; ++i) {
            int colour = currentCubeColours[i];
            /* don't draw invisible faces */
            if (g_isRotated || colour != Cube.HIDDEN) {
            	/* if rotated normal vector's Z axis is not positive value,
            	 * it is not displayed, therefore can't select */
            	if (!g_isRotated && !isVisibleFaces[i] ) {
            		continue;
            	}
                if (isDoPicking && !g_cube_selected && pickFace == i) {
                	float pickX = oldMousePt.x;
                	float pickY = height - oldMousePt.y;
            		float[] quardPoints = getProjectedPoints(gl, i, 1.0f);
            		boolean isInner = isInnerOfQuadrangle(pickX, pickY, quardPoints);

                    if (isInner) {/* the pick point is in the current cube face quadrangle :) */
                        selectedCubeLocation[0] = x; selectedCubeLocation[1] = y; selectedCubeLocation[2] = z;
                        g_selected_cube_face = i;
                        // if selected, set 4 direction operation.
                        drawArrow(g_selected_cube_face, selectedCubeLocation[0], selectedCubeLocation[1], selectedCubeLocation[2]);
                        g_cube_selected = true;
                        Log.d("Cube", "selected : " +x+", " +y+", " + z );
                    }
                }

                if (g_selected_cube_face == i && selectedCubeLocation[0]==x && selectedCubeLocation[1]==y && selectedCubeLocation[2]==z) {
                    gl.glColor4f(cubeColours[colour][0]*0.5f, cubeColours[colour][1]*0.5f, cubeColours[colour][2]*0.5f, 1.0f);
                } else {
                    gl.glColor4f(cubeColours[colour][0], cubeColours[colour][1], cubeColours[colour][2], 1.0f);
                }
                gl.glVertexPointer(3, GL10.GL_FLOAT, 0, boxBuffer);
                gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, i*4, 4);
                gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texBuff);
            }
        }
    }

	private boolean isInnerOfQuadrangle(float pickX, float pickY, float[] quardPoints) {

		/* a*x = y -> formulate simple equation. a is slope, b is y value when x is 0 */
		float[] a = new float[4];
		float[] b = new float[4];
		boolean isInner = true;

		/* get the equation of line in quadrangle,
		 * and find if the pick point is inner of the quadrangle */
		for (int n = 0; n < 4; ++n) {
		    int n2 = n+1;
		    int n3 = n+2;
		    if (n2 >= 4) n2 -= 4;
		    if (n3 >= 4) n3 -= 4;
		    float point1X = quardPoints[n*2], point1Y = quardPoints[n*2+1];
		    float point2X = quardPoints[n2*2], point2Y = quardPoints[n2*2+1];
		    float point3X = quardPoints[n3*2], point3Y = quardPoints[n3*2+1];
		    /* this case the a is infinite, so X = point1X(constant equation) */
		    if (point1X == point2X) {
		        isInner = (point3X - point1X > 0)? pickX > point1X: pickX < point1X;
		    } else {
		        float xDiffer = (point1X - point2X);
		        a[n] = (point1Y - point2Y)/xDiffer;
		        b[n] = point1Y - a[n]*point1X;
		        isInner = (a[n]*point3X + b[n] > point3Y)? a[n]*pickX + b[n] > pickY : a[n]*pickX + b[n] < pickY;
		    }
		    if (! isInner) break; /* do not waste cpu, already out of bounds */
		}
		Log.d("Cube", "pick : "+pickX+":" +pickY
		        +"\nax+b=["+a[0]+"x + "+b[0]+"\n"+a[1]+"x + "+b[1]+"\n"+a[2]+"x + "+b[2]+"\n"+a[3]+"x + "+b[3]+"\n"
		        +"\n[" + quardPoints[0]+" : "+quardPoints[1]
		        +"] \n["+ quardPoints[2]+" : "+quardPoints[3]
		        +"] \n["+ quardPoints[4]+" : "+quardPoints[5]
		        +"] \n["+ quardPoints[6]+" : "+quardPoints[7]+ "], " + isInner);
		return isInner;
	}

	private float[] getProjectedPoints(GL10 gl, int faceIndex, float scale) {
		float[] quardPoints = new float[2*4];
		projector.getCurrentProjection(gl);
		projector.getCurrentModelView(gl);
		int preIndex = faceIndex*4*3;
		for (int n = 0; n < 4; ++ n) {
		    int nn = n;
		    // 3rd and 4th exchanged in order to build quadrangle points from triangle points.
		    if (n==2) nn = 3; else if (n==3) nn = 2;
		    int index =  preIndex+nn*3;
		    float[] faceIndices = {boxVertices[index]*scale, boxVertices[++index]*scale, boxVertices[++index]*scale, 1.0f};
		    projector.project(faceIndices, 0, quardPoints, n*2);
		}
		return quardPoints;
	}

    /**
     * do motion effect to rotate cubes.
     */
    boolean doMotionEffect(Vector3f motionVector) {
        float length = motionVector.getLength();
        // if length of vector too small to be aware of users motion,
        // ignore.
        if (length < 3.0f) {
            return false;
        }

        // x, y and z axis vectors current.
        final Vector3f vx = new Vector3f( ThisRot.m[Matrix3f.XX], ThisRot.m[Matrix3f.XY], ThisRot.m[Matrix3f.XZ] );
        final Vector3f vy = new Vector3f( ThisRot.m[Matrix3f.YX], ThisRot.m[Matrix3f.YY], ThisRot.m[Matrix3f.YZ] );
        final Vector3f vz = new Vector3f( ThisRot.m[Matrix3f.ZX], ThisRot.m[Matrix3f.ZY], ThisRot.m[Matrix3f.ZZ] );

        // get dot product of motion vector for each axis vector.
        float xDot;
        xDot = motionVector.getDotProduct(vx);
        float yDot;
        yDot = motionVector.getDotProduct(vy);
        float zDot;
        zDot = motionVector.getDotProduct(vz);
        int arrowIndex = -1;
        float xDotAbs;
        xDotAbs = Math.abs(xDot);
        float yDotAbs;
        yDotAbs = Math.abs(yDot);
        float zDotAbs;
        zDotAbs = Math.abs(zDot);

        // find arrow(direction) with selected face and dot products.
        switch (g_selected_cube_face) {
        case Cube.FRONT:
        case Cube.BACK:
            if (xDotAbs > yDotAbs) {
                if (xDot > 0)
                    arrowIndex = 1;
                else
                    arrowIndex = 3;
            } else {
                if (yDot > 0)
                    arrowIndex = 0;
                else
                    arrowIndex = 2;
            }
            break;
        case Cube.LEFT:
        case Cube.RIGHT:
            if (yDotAbs > zDotAbs) {
                if (yDot > 0)
                    arrowIndex = 0;
                else
                    arrowIndex = 2;
            } else {
                if (zDot > 0)
                    arrowIndex = 1;
                else
                    arrowIndex = 3;
            }
            break;
        case Cube.TOP:
        case Cube.BOTTOM:
            if (zDotAbs > xDotAbs) {
                if (zDot > 0)
                    arrowIndex = 0;
                else
                    arrowIndex = 2;
            } else {
                if (xDot > 0)
                    arrowIndex = 1;
                else
                    arrowIndex = 3;
            }
            break;
        }
        if (arrowIndex == -1)
            return false;
        isDragging = false;
        isClicked = false;
        ProcessSelect(arrowIndex, 0, 0);
        return true;
    }

    void ProcessSelect(int index, int depth, int hits) {
        if (index < 0 || index > 4) {
            return;
        }
        setRotation(g_arrowOperations[index][0],
                g_arrowOperations[index][1],
                g_arrowOperations[index][2]);
    }
    /**
     * add rotation.
     * @param newRotateDirection a direction 1 or -1
     * @param newColumnIndex a column index for rotation axis  0,1,2,...
     * @param newRotateAxis a base axis of rotation, x,y,z axises are 0,1,2.
     */
    void setRotation(int newRotateDirection, int newColumnIndex, int newRotateAxis) {

        CubeRotation operation = new CubeRotation(newRotateAxis,
                newColumnIndex, newRotateDirection);
        rotationStack.push(operation);
    }

    void drawArrow(int face, int x, int y, int z) {
        /* this function doesn't actually draw direction 4 arrows which is shown user to make selection to rotate the cube wing
         * but just put the rotation information to determine which direction is selected later by motion vector.
         * arrow rotation information positioned 0 directs up, 1 to right, 2 to down, 3 to left.(clock-wise).
         * So, 0 and 2 or 1 and 3 are opposites. and front and back, left and right or top and bottom are opposites each.
         */
        //Log.d("Cube", "selected face:" + face + ", x=" +x +",y=" + y +",z=" + z);
        switch (face) {
        case Cube.FRONT:
            g_arrowOperations[0][0] = -1;
            g_arrowOperations[0][1] = x;
            g_arrowOperations[0][2] = CubeRotation.AXIS_X;
            g_arrowOperations[1][0] = 1;
            g_arrowOperations[1][1] = y;
            g_arrowOperations[1][2] = CubeRotation.AXIS_Y;
            g_arrowOperations[2][0] = 1;
            g_arrowOperations[2][1] = x;
            g_arrowOperations[2][2] = CubeRotation.AXIS_X;
            g_arrowOperations[3][0] = -1;
            g_arrowOperations[3][1] = y;
            g_arrowOperations[3][2] = CubeRotation.AXIS_Y;
            break;
        case Cube.BACK:
            g_arrowOperations[0][1] = x;
            g_arrowOperations[0][2] = CubeRotation.AXIS_X;
            g_arrowOperations[1][1] = y;
            g_arrowOperations[1][2] = CubeRotation.AXIS_Y;
            g_arrowOperations[2][1] = x;
            g_arrowOperations[2][2] = CubeRotation.AXIS_X;
            g_arrowOperations[3][1] = y;
            g_arrowOperations[3][2] = CubeRotation.AXIS_Y;
            // just reverse directions from front.
            g_arrowOperations[0][0] = 1;
            g_arrowOperations[1][0] = -1;
            g_arrowOperations[2][0] = -1;
            g_arrowOperations[3][0] = 1;
            break;
        case Cube.LEFT:
            g_arrowOperations[0][0] = -1;
            g_arrowOperations[0][1] = z;
            g_arrowOperations[0][2] = CubeRotation.AXIS_Z;
            g_arrowOperations[1][0] = 1;
            g_arrowOperations[1][1] = y;
            g_arrowOperations[1][2] = CubeRotation.AXIS_Y;
            g_arrowOperations[2][0] = 1;
            g_arrowOperations[2][1] = z;
            g_arrowOperations[2][2] = CubeRotation.AXIS_Z;
            g_arrowOperations[3][0] = -1;
            g_arrowOperations[3][1] = y;
            g_arrowOperations[3][2] = CubeRotation.AXIS_Y;
            break;
        case Cube.RIGHT:
            g_arrowOperations[0][1] = z;
            g_arrowOperations[0][2] = CubeRotation.AXIS_Z;
            g_arrowOperations[1][1] = y;
            g_arrowOperations[1][2] = CubeRotation.AXIS_Y;
            g_arrowOperations[2][1] = z;
            g_arrowOperations[2][2] = CubeRotation.AXIS_Z;
            g_arrowOperations[3][1] = y;
            g_arrowOperations[3][2] = CubeRotation.AXIS_Y;
            // just reverse directions from left.
            g_arrowOperations[0][0] = 1;
            g_arrowOperations[1][0] = -1;
            g_arrowOperations[2][0] = -1;
            g_arrowOperations[3][0] = 1;
            break;
        case Cube.TOP:
            g_arrowOperations[0][0] = 1;
            g_arrowOperations[0][1] = x;
            g_arrowOperations[0][2] = CubeRotation.AXIS_X;
            g_arrowOperations[1][0] = -1;
            g_arrowOperations[1][1] = z;
            g_arrowOperations[1][2] = CubeRotation.AXIS_Z;
            g_arrowOperations[2][0] = -1;
            g_arrowOperations[2][1] = x;
            g_arrowOperations[2][2] = CubeRotation.AXIS_X;
            g_arrowOperations[3][0] = 1;
            g_arrowOperations[3][1] = z;
            g_arrowOperations[3][2] = CubeRotation.AXIS_Z;
            break;
        case Cube.BOTTOM:
            g_arrowOperations[0][1] = x;
            g_arrowOperations[0][2] = CubeRotation.AXIS_X;
            g_arrowOperations[1][1] = z;
            g_arrowOperations[1][2] = CubeRotation.AXIS_Z;
            g_arrowOperations[2][1] = x;
            g_arrowOperations[2][2] = CubeRotation.AXIS_X;
            g_arrowOperations[3][1] = z;
            g_arrowOperations[3][2] = CubeRotation.AXIS_Z;
            // just reverse directions from left.
            g_arrowOperations[0][0] = -1;
            g_arrowOperations[1][0] = 1;
            g_arrowOperations[2][0] = 1;
            g_arrowOperations[3][0] = -1;
            break;

        default:
            break;
        }
    }

}
